import * as path from 'path';
import * as os from 'os';
import *  as fs from 'fs-extra';

import {App, normalizePath, Notice} from 'obsidian';
import decompress from 'decompress';
import * as parse5 from "parse5"
import { Flomo } from './flomo';
import { generateMoments } from './obIntegration/moments';
import { generateCanvas } from './obIntegration/canvas';
import {stripLeft, stripLefts, stripRight} from "./utils";
import {start} from "repl";
import Path from "path";
import GenerateHeadings from "./generateHeadings";
import { Dispatch } from './dispatch';

const FLOMO_CACHE_LOC = path.join(os.homedir(), ".flomo/cache/");
// const featureTagList = [
//     { tag: "#待办提醒/待办", prefix: () => {return "- [ ]";} },
//     { tag: "#待办提醒/已完成", prefix: () => {return "- [x]";} },
//     { tag: "#待办提醒/意向中", prefix: () => {return "- [ ]";} },
//     { tag: "#待办提醒", prefix: () => {return "- [ ]";} },
// ];
// const wrapFeatureTagPrefix = (memoContent: string, defaultPrefix: string) => {
//     const matches = featureTagList.filter(t => memoContent.indexOf(t.tag) !== -1); // memo中包含"#待办提醒/待办"的tag
//     if (matches.length > 0) {
//         if (matches[0]) {
//             memoContent = memoContent.replace(matches[0].tag, "");
//             return `${matches[0].prefix()} ${memoContent}`;
//         }
//     } else {
//         return `${defaultPrefix} ${memoContent}`;
//     }
// }
// const dispatchTagList= [ // 有顺序影响的，长的应该在前面
//   { tag: "#日常笔记/项目运营/交银保险资管工作", heading: "#### 交银保险资管工作", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, "*");}},
//   { tag: "#日常笔记/IT知识学习", heading: "### IT知识学习", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, "*");} },
//   { tag: "#日常笔记/IT开发笔记", heading: "### IT开发笔记", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, "*");} },
//   { tag: "#日常笔记/游戏影音娱乐", heading: "### 游戏影音娱乐", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, "*");} },
//   { tag: "#日常笔记/财经投资业务知识学习", heading: "### 财经投资业务知识学习", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, "*");} },
//   { tag: "#日常笔记/项目运营", heading: "### 项目运营", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, "*");} },
//   { tag: "#日常笔记", heading: "## ✒️日常笔记", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, "*");} },
//   { tag: "#思考总结", heading: "## ✏思考总结", postProcess: (memoContent, memoTime) => {return wrapFeatureTagPrefix(memoContent, `* ${memoTime.split(":").slice(0, 2).join(":")}`);} },
// ];

class TemplateString extends String {
    interpolate(params: Object) {
        const names = Object.keys(params);
        const vals = Object.values(params);
        return new Function(...names, `return \`${this}\`;`)(...vals);
    }
}

export class FlomoImporter {
    private config: Record<string, any>;
    private app: App;
    private count: number;
    private enableChecksum: boolean = false;
    private skippedCount: number;

    constructor(app: App, config: Record<string, string>) {
        this.config = config;
        this.app = app;
        this.count = 0;
        this.skippedCount = 0;
        //@ts-ignore
        this.config["baseDir"] = app.vault.adapter.basePath;
    }

    private async sanitize(path: string): Promise<string> {
        const flomoData = await fs.readFile(path, "utf8");
        const document = parse5.parse(flomoData);
        return parse5.serialize(document);
    }

    private cleanupMemoContent(memoContent) {
        return memoContent.replaceAll("\n", " ").replaceAll("\r", " ");
    }

    // private processUrl(memoContent) {
    //     return memoContent.replace(/(https?:\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|])/g, '[原文]($1)');
    // }
    //
    // private appendTag(memoContent, tag) {
    //     if (tag && tag.trim() !== "") {
    //         return `${memoContent} #${tag}`;
    //     } else {
    //         return memoContent;
    //     }
    // }

    // private dispatch(memo) {
    //     // memo.tags应该已经有了，这里是不是重复造轮子？
    //     // memo.tags是所有笔记的标签并集，并不是这条笔记的标签。
    //     const regex = /#[^\s]+/g;
    //     const tags = memo.content.match(regex);
    //     if (tags) {
    //         let memoContent;
    //         if (tags.includes("#多行")) {
    //             memoContent = memo.content.replace("#多行", "");
    //         } else {
    //             memoContent = this.cleanupMemoContent(memo.content);
    //         }
    //         memoContent = this.processUrl(memoContent);
    //         for (const tag of tags) {
    //             // JM: 只要当前memo的tag存在于dispatchTagList中，也可能是多个。但不用全匹配，只要是#待办开头的都算
    //             // const matches = dispatchTagList.filter(t => t.tag.startsWith(tag));
    //             const matches = dispatchTagList.filter(t => tag.startsWith(t.tag)); // (笔记中的tag).startsWith(dispatchTagList中的tag) #日常笔记/工作.startsWith(#日常笔记)
    //             if (matches.length > 0) {
    //                 if (matches[0]) {
    //                     memoContent = memoContent.replace(tag, "");
    //                     const subTag = stripLeft(stripLefts(tag, matches[0].tag), "/");
    //                     return {
    //                         heading: matches[0].heading,
    //                         // memoContent: `${matches[0].prefix(memo)} ${memoContent}`, // 普通的tag在memoContent中仍然是保留的
    //                         memoContent: matches[0].postProcess(memoContent, memo.time), // 普通的tag在memoContent中仍然是保留的
    //                         tag: subTag, // 这里一条memo返回一个tag是合理的，因为在这里只能处理特定的分类标签的子标签，而分类标签一定是只有一个。
    //                     };
    //                 }
    //             }
    //         }
    //     }
    //     return {
    //         filename: null,
    //         heading: null,
    //         memoContent: null,
    //         tag: null,
    //         anchor: null
    //     }
    // }

    private static findOffset(heading, content) {
        // const regex = new RegExp(`(?<=[\\n\\r])#+?${heading}(?=[\\n\\r]|$)`);
        const regex = new RegExp(`(?<=[\\n\\r])#+? ?(\\s|\\S)?${heading}(?=[\\n\\r]|$)`);
        const match = regex.exec(content);
        if (match) {
            return {startOffset: match.index, endOffset: match.index + match[0].length};
        }
        else {
            console.log(`not found heading ${heading} in: \n ${content}`);
            return null
        }
    }

    private async readImportedMemos(filepath) {
        const file = this.app.vault.getFileByPath(filepath);
        if (await this.app.vault.adapter.exists(filepath)){
            const content = await this.app.vault.read(file);
            const lines = content.trim().split('\n');
            const map = new Map();
            for (const line of lines) {
                const [id, name] = line.trim().split('->').map(item => item.trim());
                if (id && name) {
                    map.set(id, name);
                }
            }
            return map;
        } else {
            return new Map();
        }
    }

    private async writeImportedMemos(map, filepath) {
        const lines = [];
        for (const [id, name] of map.entries()) {
            lines.push(`${id} -> ${name}`);
        }
        // 将数组中的每一行连接成一个文本内容
        const fileContent = lines.join('\n');
        await this.app.vault.adapter.write(
          filepath,
          fileContent
        );
    }

    private getAttachmentFolderPath(mdFileName: string) {
        let path = new TemplateString('./attachments/${filename}').interpolate({
            filename: mdFileName
        });
        return path;
    }

    private appendGeneralTags(content: string, generalTags: string[]): string {
        let resultConent = content;
        for (const tag of generalTags) {
            resultConent = `${resultConent.trimEnd()} ${tag}`;
        }
        return resultConent
    }

    private async importMemos(flomo: Flomo, tmpDir: string, files: decompress.File[]): Promise<Flomo> {

        // const allowBilink: boolean = this.config["expOptionAllowbilink"];
        // const margeByDate: boolean = this.config["mergeByDate"];
        // @ts-ignore
        // const filesToWrite: [{filePath: string,  fileContent: string}] = [];
        const filesToWrite = new Map();
        let fileName = "";
        let fileContent = "";
        // let prevDate = "";
        const checksumFilePath = this.config["checksumFilePath"].trim();
        let checksumMemos = new Map();
        if (this.enableChecksum) {
            checksumMemos = await this.readImportedMemos(checksumFilePath);
        }
        for (const [idx, memo] of flomo.memos.entries()) {
            try {
                // 对于已经导入过的，跳过
                console.log(`processing ${memo.title}`)
                if (this.enableChecksum) {
                    if (checksumMemos.has(memo.datetime)) {
                        this.skippedCount += 1;
                        continue;
                    }
                }

                let dispatch = await new Dispatch(this.app, memo, this.config["templates"]).dispatch();
                // console.log(dispatchResult);
                if (dispatch.filename) {
                    const file = this.app.metadataCache.getFirstLinkpathDest(
                        dispatch.filename,
                        ''
                    );
                    if (file) {
                        // 读取笔记内容，一篇笔记必须只读取一次
                        if (fileName != dispatch.filename || fileName == "") {
                            fileContent = await this.app.vault.read(file);
                            fileName = dispatch.filename;
                        }
                        // 清理文件中原来导入的数据
                        // 找和memoContent一致的行，去掉#flomo后比较内容是不是一致，如果一致就删除原来一行，后续添加回来（不对，如果flomo调整就又不一致了）
                        // 算了，还是手动打#fixed标签吧。
                        // 其实只要在ob中有ID，那么就能确切找到ob中哪一行和flomo中匹配。但是价值只是不会多删一篇笔记中的其他条目
                        const lines = fileContent.trim().split('\n');
                        for (const line of lines) {
                            if (line.includes("#flomo") && !line.includes("#fixed")) { // 一旦有#fixed标签，则保留待后续手工merge
                                fileContent = fileContent.replaceAll(line, "");
                                // 只要整个笔记中有一个#fixed，那么所所有条目都会被添加上#merge
                                // if (line.includes("#fixed")){
                                //     if (!dispatchResult.tags.includes("#merge")) {
                                //         dispatchResult.tags.push("#merge")
                                //     }
                                // } else {
                                //     fileContent = fileContent.replaceAll(line, "");
                                // }
                            }
                        }
                        // 下面这里主要是处理图片
                        let mdFolderPath: string = Path.dirname(file.path);
                        const attachmentFolderPath = Path.join(mdFolderPath, this.getAttachmentFolderPath(file.basename));
                        const decompressRootFolder = stripRight(files[0].path, '/');
                        for (const f of files) {
                            // TODO: 这里只要当天有笔记可以被dispath，那么这天其他笔记中的图片就会被复制过来，算是一个bug吧
                            // if (f.type == "directory" && f.path.endsWith(`/file/${file.basename}/`)) {
                            if (f.type === "file" && f.path.startsWith(`${decompressRootFolder}/file/${file.basename}/`)) {
                                console.debug(`DEBUG: copying from ${tmpDir}/${f.path} to ${this.config["baseDir"]}/${attachmentFolderPath}`)
                                if (!await this.app.vault.adapter.exists(attachmentFolderPath))
                                    await this.app.vault.adapter.mkdir(attachmentFolderPath);
                                await fs.copy(normalizePath(Path.join(tmpDir, f.path)), normalizePath(Path.join(this.config["baseDir"], attachmentFolderPath, Path.basename(f.path))));
                                break;
                            }
                        }
                        if (dispatch.heading) {
                            // 没有找到目标heading，则要用cascade heading方式带出上级heading
                            if (!FlomoImporter.findOffset(dispatch.heading, fileContent)) {
                                const vaultStartLocation = "1_Inbox/Diary";
                                const ingoredHeaders = ["## 🕓今日事项", "## ✒️日常笔记", "## 🔔待办提醒", "## ✏思考总结"];
                                const ignoredSuffixArray = [".excalidraw.md"];
                                // 如果没找到heading，会报错并且跳过这条memo
                                let generateHeadings = new GenerateHeadings(this.app, vaultStartLocation, ingoredHeaders, ignoredSuffixArray);
                                fileContent = generateHeadings.selectSuggestion(dispatch.heading, fileContent);  // heading = "#### 交银保险资管工作"
                            }
                            // 带出cascade heading后再找一次heading
                            let position = FlomoImporter.findOffset(dispatch.heading, fileContent);
                            const startOffset = position.endOffset;
                            fileContent = fileContent.slice(0, startOffset).trimEnd() + `\n${this.appendGeneralTags(dispatch.memoContent, dispatch.tags)}\n` + fileContent.slice(startOffset).trimStart();
                        }
                        filesToWrite.set(file.path, fileContent);
                        if (this.enableChecksum) {
                            checksumMemos.set(memo.datetime, this.cleanupMemoContent(memo.content));
                        }
                    } else {
                        // 如果file不存在，应该是整篇笔记
                        await this.app.vault.create(dispatch.filename, dispatch.memoContent);
                    }
                    this.count += 1;
                }
            } catch (err) {
                console.error(`memo ${memo.title} import error, skipped! Details:\n${err}`);
                new Notice(`memo ${memo.title} import error, skipped! Details:\n${err}`);
                this.skippedCount += 1;
                // throw err;
            }

        }
        if (this.enableChecksum) {
            await this.writeImportedMemos(checksumMemos, checksumFilePath);
        }

        for (const [key, value] of filesToWrite) {
            const officalValue = value.replaceAll("#flo_temp_mo", "#flomo");
            await this.app.vault.adapter.write(
              key, officalValue
            );
        }

        new Notice(`Imported ${this.count} memos, skipped ${this.skippedCount} memos`)
        return flomo;
    }

    async import(): Promise<Flomo> {

        // 1. create workspace
        const tmpDir = path.join(FLOMO_CACHE_LOC, "data")
        await fs.mkdirp(tmpDir);

        // 2. unzip flomo_backup.zip to workspace
        const files = await decompress(this.config["rawDir"], tmpDir)

        // 3. copy attachments to ObVault
        // const obVaultConfig = await fs.readJson(`${this.config["baseDir"]}/${this.app.vault.configDir}/app.json`)
        // const attachementDir = obVaultConfig["attachmentFolderPath"] + "/flomo/";
        //
        // for (const f of files) {
        //     if (f.type == "directory" && f.path.endsWith("/file/")) {
        //         console.debug(`DEBUG: copying from ${tmpDir}/${f.path} to ${this.config["baseDir"]}/${attachementDir}`)
        //         await fs.copy(`${tmpDir}/${f.path}`, `${this.config["baseDir"]}/${attachementDir}`);
        //         break
        //     }
        //
        // }

        // 4. import Memos
        const backupData = await this.sanitize(`${tmpDir}/${files[0].path}/${this.config["indexFilename"]}`);
        const startDate =this.config.hasOwnProperty("startDate") && this.config["startDate"] !== "" ? new Date(this.config["startDate"]): null;
        const endDate = this.config.hasOwnProperty("endDate") && this.config["endDate"] !== "" ? new Date(this.config["endDate"]): null;
        const flomo = new Flomo(backupData, startDate, endDate);

        const memos = await this.importMemos(flomo, tmpDir, files);

        // // 5. Ob Intergations
        // // If Generate Moments
        // if (this.config["optionsMoments"] != "skip") {
        //     await generateMoments(app, memos, this.config);
        // }
        //
        // // If Generate Canvas
        // if (this.config["optionsCanvas"] != "skip") {
        //     await generateCanvas(app, memos, this.config);
        // }

        // 6. Cleanup Workspace
        await fs.remove(tmpDir);

        // 7. Save batch process progress
        // const filePath = "0_Workshop/importer/batch.md";
        // const fileContent = "2024-01-01";
        // await this.app.vault.adapter.write(
        //   filePath,
        //   fileContent
        // );

        return flomo

    }

}
